package com.czkevin.guicontrolsapi;

import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.metadata.FixedMetadataValue;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.LinkedList;

public class InputWindowManager extends PacketAdapter implements Listener {

    private static Method CraftPlayer_getHandle = null;
    private static Method EntityPlayer_nextContainerCounter = null;

    private final GUIControlsAPI plugin;
    private ProtocolManager protocol;

    public static int ANVIL_WINDOW_ID = 7;

    public InputWindowManager(GUIControlsAPI plugin) {
        super(plugin,
                PacketType.Play.Server.OPEN_WINDOW, // prevent server operations
                PacketType.Play.Server.CLOSE_WINDOW,
                PacketType.Play.Server.WINDOW_DATA,
                PacketType.Play.Server.WINDOW_ITEMS,
                PacketType.Play.Server.SET_SLOT,

                PacketType.Play.Client.ITEM_NAME,
                PacketType.Play.Client.CLOSE_WINDOW,
                PacketType.Play.Client.WINDOW_CLICK
        );
        this.plugin = plugin;
    }

    public void onReload() {
        protocol = ProtocolLibrary.getProtocolManager();
        for(Player p : Bukkit.getOnlinePlayers()) {
            p.removeMetadata(InputWindowTrait.META_KEY, plugin);
            p.setMetadata(InputWindowTrait.META_KEY, new FixedMetadataValue(plugin, new InputWindowTrait(p)));
        }
    }

    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
    public void onPlayerJoin(PlayerJoinEvent e) {
        e.getPlayer().setMetadata(InputWindowTrait.META_KEY, new FixedMetadataValue(plugin, new InputWindowTrait(e.getPlayer())));
    }

    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
    public void onWindowOpen(InventoryOpenEvent e) {
        // not by us!
        InputWindowTrait trait = (InputWindowTrait) e.getPlayer().getMetadata(InputWindowTrait.META_KEY).get(0).value();
        if(trait.fakeWindowOpened) {
            e.setCancelled(true);
            trait.sendOpenWindow();
        }
    }

    public boolean isOpened(Player p) {
        InputWindowTrait trait = (InputWindowTrait) p.getMetadata(InputWindowTrait.META_KEY).get(0).value();
        return trait.fakeWindowOpened;
    }

    /*@EventHandler(priority = EventPriority.MONITOR)
    public void onWindowClose(InventoryCloseEvent e) {
        // not by us!
        InputWindowTrait trait = (InputWindowTrait) e.getPlayer().getMetadata(InputWindowTrait.META_KEY).get(0).value();
        if(trait.fakeWindowOpened) {
            Bukkit.getScheduler().runTask(plugin, () -> {
                e.getPlayer().closeInventory();
                trait.sendOpenWindow();
            });
        }
    }*/

    public void openInput(Player player, String title, String name, InputCallback callback, Window parent) {
        InputWindowTrait trait = (InputWindowTrait) player.getMetadata(InputWindowTrait.META_KEY).get(0).value();
        if(trait.fakeWindowOpened) {
            trait.parentWindow = null; // don't open that!
            closeAndFinishUp(player, trait);
        }
        trait.fakeWindowOpened = true;
        trait.parentWindow = parent;
        trait.title = title;
        trait.inputName = name;
        trait.just_opened = true;
        trait.value = null;
        trait.callback = callback;
        trait.last_update = 0L;
        player.closeInventory();
        trait.sendOpenWindow();
    }

    @Override
    public void onPacketSending(PacketEvent event) {
        /*if(event.getPacketType() == PacketType.Play.Server.OPEN_WINDOW) {
            System.out.println("=================================");
            System.out.println("Open Window");
            System.out.println("OpenWindow ID = " + event.getPacket().getIntegers().read(0));
            System.out.println("Type: " + event.getPacket().getStrings().read(0));
            System.out.println("Slots: " + event.getPacket().getIntegers().read(1));
            WrappedChatComponent c = event.getPacket().getChatComponents().read(0);
            System.out.println("Title: " + c.toString());
        } else if (event.getPacketType() == PacketType.Play.Server.WINDOW_ITEMS) {
            System.out.println("=================================");
            System.out.println("Window Items");
            System.out.println("OpenWindow ID = " + event.getPacket().getIntegers().read(0));
            System.out.println("Items: " + event.getPacket().getItemListModifier().read(0).size());
        }*/
        InputWindowTrait trait = (InputWindowTrait) event.getPlayer().getMetadata(InputWindowTrait.META_KEY).get(0).value();
        if (trait.fakeWindowOpened && event.getPacket().getIntegers().read(0) != trait.fakeWindowId) {
            event.setCancelled(true);
        }
    }

    @Override
    public void onPacketReceiving(PacketEvent event) {
        InputWindowTrait trait = (InputWindowTrait) event.getPlayer().getMetadata(InputWindowTrait.META_KEY).get(0).value();
        if (trait.fakeWindowOpened) {
            event.setCancelled(true); // prevent going to internal system

            PacketContainer packet = event.getPacket();

            // item name
            if (packet.getType() == PacketType.Play.Client.ITEM_NAME) {
                if(trait.just_opened) {
                    trait.just_opened = false;
                    trait.refreshWindowItems();
                    return;
                }
                trait.value = packet.getStrings().read(0);
                boolean force = false;
                /*if(trait.value.length() > 3 && trait.value.startsWith("\u00a7")) {
                    trait.value = trait.value.replace("\u00a7", "").replace(" ", "");
                    force = true;
                }*/
                trait.value = ChatColor.stripColor(trait.value);
                // System.out.println("Input > " + trait.value);

                trait.refreshWindowItems(force);
                return;
            }

            // close window
            if (packet.getType() == PacketType.Play.Client.CLOSE_WINDOW) {
                closeAndFinishUp(event.getPlayer(), trait);
                return;
            } // end of if close window

            // window click
            if (packet.getType() == PacketType.Play.Client.WINDOW_CLICK) {

                trait.refreshWindowItems(true);
            }
        } // end of if fake window opened
    }

    private void closeAndFinishUp(Player player, InputWindowTrait trait) {
        trait.fakeWindowOpened = false;
        trait.fakeWindowId = -1;
        if (trait.parentWindow != null) {
            Bukkit.getScheduler().runTask(plugin, () -> {
                trait.parentWindow.open(player, null);
            });
        }
        if (trait.callback != null) {
            Bukkit.getScheduler().runTask(plugin, () -> {
                trait.callback.onFinish(trait.inputName, trait.value);
            });
        }
    }




    public static int nextWindowId(Player player) {
        try {
            if(CraftPlayer_getHandle == null) {
                CraftPlayer_getHandle = player.getClass().getDeclaredMethod("getHandle");
                CraftPlayer_getHandle.setAccessible(true);
            }
            Object entityPlayer = CraftPlayer_getHandle.invoke(player);
            if (EntityPlayer_nextContainerCounter == null) {
                EntityPlayer_nextContainerCounter = entityPlayer.getClass().getDeclaredMethod("nextContainerCounter");
                EntityPlayer_nextContainerCounter.setAccessible(true);
            }
            return (int) EntityPlayer_nextContainerCounter.invoke(entityPlayer);
        } catch (Exception e) {
            e.printStackTrace();
            return (int) (System.currentTimeMillis() & 100);
        }
    }

    private class InputWindowTrait {
        public static final String META_KEY = "gui-controls::input-window::data";

        public final Player player;

        public boolean fakeWindowOpened = false;
        public int fakeWindowId = -1;
        public Window parentWindow;
        public long last_update = 0L;

        public boolean just_opened = false; // fix for auto send

        public String inputName;
        public String title;
        public String value;
        public InputCallback callback;

        public InputWindowTrait(Player player) {
            this.player = player;
        }

        public int nextWindowId() {
            return InputWindowManager.nextWindowId(player);
        }

        public void sendOpenWindow() {
            if(!fakeWindowOpened) return;
            if(fakeWindowId == -1) fakeWindowId = nextWindowId();
            PacketContainer windowOpen = new PacketContainer(PacketType.Play.Server.OPEN_WINDOW);
            windowOpen.getIntegers()
                .write(0, fakeWindowId)
                .write(1, ANVIL_WINDOW_ID);
            windowOpen.getChatComponents().write(0, WrappedChatComponent.fromText(title));

            try {
                protocol.sendServerPacket(player, windowOpen);
            } catch (InvocationTargetException e) {
                e.printStackTrace();
                fakeWindowOpened = false;
                fakeWindowId = -1;
                closeAndFinishUp(player, this);
                return;
            }

            refreshWindowItems();
        }


        public void refreshWindowItems() {refreshWindowItems(false);}
        public void refreshWindowItems(boolean force) {
            if(System.currentTimeMillis() - last_update < 200 && !force) {
                return;
            }
            last_update = System.currentTimeMillis();
            PacketContainer windowItems = new PacketContainer(PacketType.Play.Server.WINDOW_ITEMS);
            windowItems.getIntegers().write(0, fakeWindowId);
            ItemStack titleItem = new ItemStack(Material.WRITABLE_BOOK);
            {
                ItemMeta m = titleItem.getItemMeta();
                //m.setDisplayName(value == null ? "\u00a70 " : value);
                m.setDisplayName(value == null ? inputName : value);
                m.addEnchant(Enchantment.DURABILITY, 1, true);
                m.setLore(Arrays.asList(
                        "\u00a78\u00a7m=========================",
                        "\u00a7e[ \u00a76\u00a7l" + title + " \u00a7e]",
                        "\u00a77Type in your value and",
                        "\u00a77press \u00a7a\u00a7lESC \u00a77to confirm! "
                ));
                m.addItemFlags(ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_ENCHANTS);
                titleItem.setItemMeta(m);
            }
            LinkedList<ItemStack> items = new LinkedList<>();
            items.add(titleItem);
            for(int i = 0; i < 38; i++) items.add(Window.ITEM_SPACE);
            windowItems.getItemListModifier().write(0, items);

            PacketContainer emptySlot = new PacketContainer(PacketType.Play.Server.SET_SLOT);
            emptySlot.getIntegers()
                .write(0, -1)
                .write(1, -1);

            try {
                protocol.sendServerPacket(player, windowItems);
                protocol.sendServerPacket(player, emptySlot);
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }

    public interface InputCallback {
        void onFinish(String name, String value);
    }
}
